package com.cat.tiger.jdbcproxy;

import com.mysql.jdbc.CommunicationsException;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.SQLNestedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;

public class JdbcTemplatePool {

    class DiagnosStatus implements Runnable {
        private int index;

        public DiagnosStatus(int index) {
            this.index = index;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    boolean lastStatus = (Boolean) dsStatus[index].get("connect");
                    boolean statusOk = diagnose();
                    if (statusOk) {
                        if (lastStatus != statusOk) {
                            dsStatus[index].put("connect", true);
                            BasicDataSource dataSource = (BasicDataSource) dsStatus[index].get("ds");
                            JdbcTemplate jdbc = new JdbcTemplate(dataSource);
                            dsStatus[index].put("jdbc", jdbc);
                        }
                        Thread.sleep(1000);
                    } else {
                        dsStatus[index].put("connect", false);
                        Thread.sleep(5000);
                    }
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    log.error("diagnose [" + index + "] err : {}", e.getMessage());
                }
            }
        }

        private boolean diagnose() {
            Map<String, Object> dsStatusMap = dsStatus[index];
            BasicDataSource ds = (BasicDataSource) dsStatusMap.get("ds");
            long begin = System.currentTimeMillis();
            boolean rtn = connectStatus(ds);
            long now = System.currentTimeMillis();
            if ((now - traceTimeRecord[index]) > 30000) {
                traceTimeRecord[index] = now;
                if (traceInfo) {
                    log.info("db[" + index + "] slave tracetime=======> " + now + " call count =====>"
                            + callCount[index].get() + "  DiagnosResult: " + rtn + " cost:" + (now - begin));
                }
            }
            return rtn;

        }

    }

    private static final Logger log = LoggerFactory.getLogger(JdbcTemplatePool.class);

    private AtomicLong atomicLong = new AtomicLong();

    private AtomicLong[] callCount = null;

    private long[] traceTimeRecord = null;

    private boolean traceInfo = false;

    private Map<String, Object>[] dsStatus;

    public JdbcTemplatePool() {
        init();
    }

    public JdbcTemplate chooseJdbc() {
        JdbcTemplate rtn = null;
        for (int i = 0; i < dsStatus.length; i++) {
            long seed = atomicLong.incrementAndGet();
            int segment = (int) (seed % dsStatus.length);
            if ((Boolean) (dsStatus[segment].get("connect"))) {
                rtn = (JdbcTemplate) dsStatus[segment].get("jdbc");
                callCount[segment].getAndIncrement();
                break;
            }
        }
        return rtn;
    }

    public long[] getCallCount() {
        long[] rtn = new long[callCount.length];
        for (int i = 0; i < rtn.length; i++) {
            rtn[i] = callCount[i].get();
        }
        return rtn;
    }

    private boolean connectStatus(BasicDataSource ds) {
        Connection con = null;
        Statement st = null;
        boolean rtn = false;
        try {
            con = ds.getConnection();
            st = con.createStatement();
            st.setQueryTimeout(2); // wait 2 seconds
            st.execute("SELECT NOW()");
            st.close();
            rtn = true;
        } catch (CommunicationsException e) {
            log.error("CommunicationsException connectStatus ===========>{}", e.getMessage());
        } catch (com.mysql.jdbc.exceptions.jdbc4.CommunicationsException e) {
            log.error("jdbc4CommunicationsException connectStatus ===========>{}", e.getMessage());
        } catch (SQLNestedException e) {
            log.error("SQLNestedException connectStatus ===========>{}", e.getMessage());
        } catch (SQLException e) {
            log.error("SQLException connectStatus ===========>{}", e.getMessage());
        } finally {
            try {
                con.close();
            } catch (Exception e) {
                log.error("close connection Excepeion connectStatus ===========>{}", e.getMessage());
            }
        }
        return rtn;
    }

    private void init() {
        ApplicationContext context = new ClassPathXmlApplicationContext();

        try {
            String[] files = {"application.properties", "jdbc.properties"};
            
            int slaveCnt = 0;
            for (String r : files) {
                try {
                    Properties p = new Properties();
                    p.load(getClass().getClassLoader().getResourceAsStream(r));
                    String cntValue = (String) p.get("jdbc.slave.count");
                    if (cntValue != null) {
                        slaveCnt = Integer.parseInt(cntValue);
                    }
                    String traceInfoValue = (String) p.get("jdbcpool.trace.info");
                    if (traceInfoValue != null) {
                        // "jdbcpool.trace.info"的值可能会被property文件覆盖
                        if ("on".equals(traceInfoValue)) {
                            traceInfo = true;
                        } else {
                            traceInfo = false;
                        }
                    }

                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    log.info("no resource file ");
                }
            }

            Map<String, String> paras = new HashMap<String, String>();
            Map<String, String> keysMap = new HashMap<String, String>();
            keysMap.put("driver", "jdbc.driver");
            for (int i = 0; i < slaveCnt; i++) {
                keysMap.put("url" + (i + 1), "jdbc.slave" + (i + 1) + ".url");
                keysMap.put("username" + (i + 1), "jdbc.slave" + (i + 1) + ".username");
                keysMap.put("password" + (i + 1), "jdbc.slave" + (i + 1) + ".password");
            }
            keysMap.put("maxIdle", "dbcp.maxIdle");
            keysMap.put("maxActive", "dbcp.maxActive");
            for (String r : files) {
                try {
                    Properties p = new Properties();
                    p.load(getClass().getClassLoader().getResourceAsStream(r));
                    for (String key : keysMap.keySet()) {
                        String value = (String) p.get(keysMap.get(key));
                        if (value != null) {
                            paras.put(keysMap.get(key), value);
                        }
                    }
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    log.info("no resource file ");
                }
            }
            dsStatus = new HashMap[slaveCnt];
            callCount = new AtomicLong[slaveCnt];
            traceTimeRecord = new long[slaveCnt];
            for (int i = 0; i < slaveCnt; i++) {
                BasicDataSource dataSource = new BasicDataSource();
                dataSource.setDriverClassName(paras.get(keysMap.get("driver")));
                dataSource.setUrl(paras.get(keysMap.get("url" + (i + 1))));
                dataSource.setUsername(paras.get(keysMap.get("username" + (i + 1))));
                dataSource.setPassword(paras.get(keysMap.get("password" + (i + 1))));
                dataSource.setMaxIdle(Integer.parseInt(paras.get(keysMap.get("maxIdle"))));
                dataSource.setMaxActive(Integer.parseInt(paras.get(keysMap.get("maxActive"))));
                dataSource.setDefaultAutoCommit(true);
                dataSource.setTimeBetweenEvictionRunsMillis(3600000);
                dataSource.setMinEvictableIdleTimeMillis(3600000);
                JdbcTemplate jdbc = new JdbcTemplate(dataSource);
                dsStatus[i] = new HashMap<String, Object>();
                dsStatus[i].put("ds", dataSource);
                // boolean status1 = connectStatus(dataSource);
                dsStatus[i].put("connect", true);
                dsStatus[i].put("jdbc", jdbc);
            }
            for (int i = 0; i < slaveCnt; i++) {
                callCount[i] = new AtomicLong();
                traceTimeRecord[i] = 0;
                DiagnosStatus ds = new DiagnosStatus(i);
                Thread t = new Thread(ds);
                t.start();
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            log.error("JdbcProxy Init Err : {}", e.getMessage());
        }
    }
}
